﻿using System;
using System.Data;
using System.Data.Common;
using System.IO;

using Microsoft.VisualStudio.TestTools.UnitTesting;

using PublicDomain.Data.Csv;
using PublicDomain.Framework.UnitTesting;
using PublicDomain.Framework.UnitTesting.DataDrivenTest;

namespace PublicDomain.UnitTest.Data.Csv
{
    [TestClass]
    [DeploymentItem("CsvDataReaderAdapterTestInput.csv")]
    [DeploymentItem("CsvDataReaderAdapterTestInputSchemata.xml")]
    public class CsvDataReaderAdapterTest
    {
        public TestContext TestContext { get; set; }

        [TestMethod]
        [Description("Tests method CsvDataReaderAdapter.ReadToDataTable")]
        [DeploymentItem("CsvDataReaderAdapterTestData.mdb")]
        [DataSource("System.Data.OleDb", "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=|DataDirectory|\\CsvDataReaderAdapterTestData.mdb", "ReadToDataTableTestData", DataAccessMethod.Sequential)]
        public void ReadToDataTableTest()
        {
            var dataDrivenTest = new DataDrivenTest<ReadToDataTableTestParams, ReadToDataTableTestExpectedResults>(this.TestContext);

            dataDrivenTest.ExecuteTest(() =>
            {
                var testParams = dataDrivenTest.TestParams;
                var expectedResults = dataDrivenTest.ExpectedResults;

                var schemataFilePath = Path.Combine(this.TestContext.TestDeploymentDir, testParams.ConnectionSchemataFileName);
                var connectionString = String.Format("Database={0};SchemataFilePath={1}", this.TestContext.TestDeploymentDir, schemataFilePath);

                var command = new CsvCommand(testParams.CommandText, new CsvConnection(connectionString));

                var dataReader = (CsvDataReader)command.ExecuteReader();

                DataTableMapping defaultDataTableMapping;
                using (var destinationDataTable = CsvDataReaderAdapter.CreateDataTable(testParams.TableName, dataReader.CsvDataFormat.CsvColumnsDefinitions, out defaultDataTableMapping))
                {
                    int chunkCount = 0;
                    while (CsvDataReaderAdapter.ReadToDataTable(dataReader, destinationDataTable, defaultDataTableMapping, 5 /*TestParam*/, null, null /*TestParam*/))
                    {
                        chunkCount++;
                    }

                    Assert.AreEqual(2, chunkCount);
                }
            });
        }

        [TestMethod]
        [Description("Tests method CsvDataReaderAdapter.CreateDataTable in the case of argument dataReader is null.")]
        public void ReadToDataTableDataReaderArgumentNullExceptionTest()
        {
            var destinationDataTable = new DataTable();
            var defaultDataTableMapping = new DataTableMapping();

            AssertExtensions.ThrowsArgumentNullException(
                () => CsvDataReaderAdapter.ReadToDataTable(null, destinationDataTable, defaultDataTableMapping, 5, null, null),
                "dataReader");
        }

        [TestMethod]
        [Description("Tests method CsvDataReaderAdapter.CreateDataTable in the case of argument destinationDataTable is null.")]
        public void ReadToDataTableDestinationDataTableArgumentNullExceptionTest()
        {
            var dataReader = new CsvDataReader("CsvDataReaderAdapterTestInput.csv");
            var defaultDataTableMapping = new DataTableMapping();

            AssertExtensions.ThrowsArgumentNullException(
                () => CsvDataReaderAdapter.ReadToDataTable(dataReader, null, defaultDataTableMapping, 5, null, null),
                "destinationDataTable");
        }

        [TestMethod]
        [Description("Tests method CsvDataReaderAdapter.CreateDataTable in the case of argument dataTableMapping is null.")]
        public void ReadToDataTableDataTableMappingArgumentNullExceptionTest()
        {
            var dataReader = new CsvDataReader("CsvDataReaderAdapterTestInput.csv");
            var destinationDataTable = new DataTable();

            AssertExtensions.ThrowsArgumentNullException(
                () => CsvDataReaderAdapter.ReadToDataTable(dataReader, destinationDataTable, null, 5, null, null),
                "dataTableMapping");
        }

        [TestMethod]
        [Description("Tests read data row error handling by the method CsvDataReaderAdapter.ReadToDataTable")]
        [DeploymentItem("CsvDataReaderAdapterTestDataRowErrorInput.csv")]
        public void ReadToDataTableReadDataRowErrorHandlerTest()
        {
            var schemataFilePath = Path.Combine(this.TestContext.TestDeploymentDir, "CsvDataReaderAdapterTestInputSchemata.xml");
            var connectionString = String.Format("Database={0};SchemataFilePath={1}", this.TestContext.TestDeploymentDir, schemataFilePath);

            var command = new CsvCommand("CsvDataReaderAdapterTestDataRowErrorInput.csv", new CsvConnection(connectionString));

            var dataReader = (CsvDataReader)command.ExecuteReader();

            DataTableMapping defaultDataTableMapping;

            using (var destinationDataTable = CsvDataReaderAdapter.CreateDataTable("CsvDataReaderAdapterTestDataRowErrorInput.csv", dataReader.CsvDataFormat.CsvColumnsDefinitions, out defaultDataTableMapping))
            {
                int chunkCount = 0;
                while (CsvDataReaderAdapter.ReadToDataTable(dataReader, destinationDataTable, defaultDataTableMapping, 5 /*TestParam*/, this.ReadDataRowHandler, this.ReadToDataTableReadDataRowErrorHandler))
                {
                    chunkCount++;
                }
            }
        }

        [TestMethod]
        [Description("Tests read data row error handling with rethrow by the method CsvDataReaderAdapter.ReadToDataTable")]
        [DeploymentItem("CsvDataReaderAdapterTestDataRowErrorInput.csv")]
        public void ReadToDataTableReadDataRowRethrowErrorHandlerTest()
        {
            var schemataFilePath = Path.Combine(this.TestContext.TestDeploymentDir, "CsvDataReaderAdapterTestInputSchemata.xml");
            var connectionString = String.Format("Database={0};SchemataFilePath={1}", this.TestContext.TestDeploymentDir, schemataFilePath);

            var command = new CsvCommand("CsvDataReaderAdapterTestDataRowErrorInput.csv", new CsvConnection(connectionString));

            var dataReader = (CsvDataReader)command.ExecuteReader();

            DataTableMapping defaultDataTableMapping;

            using (var destinationDataTable = CsvDataReaderAdapter.CreateDataTable("CsvDataReaderAdapterTestDataRowErrorInput.csv", dataReader.CsvDataFormat.CsvColumnsDefinitions, out defaultDataTableMapping))
            {
                AssertExtensions.Throws<CsvDataRowException>(
                    () =>
                    {
                        int chunkCount = 0;
                        while (CsvDataReaderAdapter.ReadToDataTable(dataReader, destinationDataTable, defaultDataTableMapping, 5 /*TestParam*/, this.ReadDataRowHandler, this.ReadToDataTableReadDataRowRethrowErrorHandler))
                        {
                            chunkCount++;
                        }
                    },
                    "The text '31Oops' cannot be parsed as Int32.");
            }
        }

        [TestMethod]
        [Description("Tests read data row error handling by the method CsvDataReaderAdapter.ReadToDataTable in the case of not provided error handler.")]
        [DeploymentItem("CsvDataReaderAdapterTestDataRowErrorInput.csv")]
        public void ReadToDataTableReadDataRowRethrowNullErrorHandlerTest()
        {
            var schemataFilePath = Path.Combine(this.TestContext.TestDeploymentDir, "CsvDataReaderAdapterTestInputSchemata.xml");
            var connectionString = String.Format("Database={0};SchemataFilePath={1}", this.TestContext.TestDeploymentDir, schemataFilePath);

            var command = new CsvCommand("CsvDataReaderAdapterTestDataRowErrorInput.csv", new CsvConnection(connectionString));

            var dataReader = (CsvDataReader)command.ExecuteReader();

            DataTableMapping defaultDataTableMapping;

            using (var destinationDataTable = CsvDataReaderAdapter.CreateDataTable("CsvDataReaderAdapterTestDataRowErrorInput.csv", dataReader.CsvDataFormat.CsvColumnsDefinitions, out defaultDataTableMapping))
            {
                AssertExtensions.Throws<CsvDataRowException>(
                    () =>
                    {
                        int chunkCount = 0;
                        while (CsvDataReaderAdapter.ReadToDataTable(dataReader, destinationDataTable, defaultDataTableMapping, 5 /*TestParam*/, null, null))
                        {
                            chunkCount++;
                        }
                    },
                    "The text '31Oops' cannot be parsed as Int32.");
            }
        }

        [TestMethod]
        [Description("Tests method CsvDataReaderAdapter.CreateDataTable.")]
        [DeploymentItem("CsvDataReaderAdapterTestData.mdb")]
        [DataSource("System.Data.OleDb", "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=|DataDirectory|\\CsvDataReaderAdapterTestData.mdb", "CreateDataTableTestData", DataAccessMethod.Sequential)]
        public void CreateDataTableTest()
        {
            var dataDrivenTest = new DataDrivenTest<CreateDataTableTestParams, CreateDataTableTestExpectedResults>(this.TestContext);

            dataDrivenTest.ExecuteTest(() =>
            {
                var testParams = dataDrivenTest.TestParams;
                var expectedResults = dataDrivenTest.ExpectedResults;

                var schemataFilePath = Path.Combine(this.TestContext.TestDeploymentDir, testParams.ConnectionSchemataFileName);
                var connectionString = String.Format("Database={0};SchemataFilePath={1}", this.TestContext.TestDeploymentDir, schemataFilePath);

                var command = new CsvCommand(testParams.CommandText, new CsvConnection(connectionString));

                var dataReader = (CsvDataReader)command.ExecuteReader();

                DataTableMapping defaultDataTableMapping;

                using (var actual = CsvDataReaderAdapter.CreateDataTable(testParams.TableName, dataReader.CsvDataFormat.CsvColumnsDefinitions, out defaultDataTableMapping))
                {
                    Assert.AreEqual(expectedResults.TableName, actual.TableName);
                    Assert.AreEqual(dataReader.CsvDataFormat.CsvColumnsDefinitions.Count, actual.Columns.Count);

                    foreach (var csvColumnsDefinition in dataReader.CsvDataFormat.CsvColumnsDefinitions)
                    {
                        var dataTableColumn = actual.Columns[csvColumnsDefinition.ColumnName];

                        Assert.IsTrue(dataTableColumn.AllowDBNull);
                        Assert.AreEqual(dataTableColumn.DataType, csvColumnsDefinition.FieldType);
                    }
                }
            });
        }

        [TestMethod]
        [Description("Tests method CsvDataReaderAdapter.CreateDataTable in the case of argument csvColumnsDefinitions is null.")]
        public void CreateDataTableCsvColumnsDefinitionsArgumentNullExceptionTest()
        {
            DataTableMapping defaultDataTableMapping;

            AssertExtensions.ThrowsArgumentNullException(
                () => CsvDataReaderAdapter.CreateDataTable("Table1", null, out defaultDataTableMapping),
                "csvColumnsDefinitions");
        }

        private void ReadDataRowHandler(DataRow dataRow, int csvDataRowIndex)
        {
            Assert.IsNotNull(dataRow);
            Assert.IsTrue(csvDataRowIndex > 0);
        }

        private bool ReadToDataTableReadDataRowErrorHandler(Exception exception, int dataRowIndex)
        {
            Assert.IsNotNull(exception);
            Assert.IsTrue(dataRowIndex > 0);

            return false;
        }

        private bool ReadToDataTableReadDataRowRethrowErrorHandler(Exception exception, int dataRowIndex)
        {
            Assert.IsNotNull(exception);
            Assert.IsTrue(dataRowIndex > 0);

            return true;
        }

        private class ReadToDataTableTestParams
        {
            [DataTableColumnName("TestParamCommandText")]
            public string CommandText { get; set; }

            [DataTableColumnName("TestParamTableName")]
            public string TableName { get; set; }

            [DataTableColumnName("TestParamConnectionSchemataFileName")]
            public string ConnectionSchemataFileName { get; set; }
        }

        private class ReadToDataTableTestExpectedResults
        {
            [DataTableColumnName("ExpectedResultChunkCount")]
            public int ChunkCount { get; set; }
        }

        private class CreateDataTableTestParams
        {
            [DataTableColumnName("TestParamCommandText")]
            public string CommandText { get; set; }

            [DataTableColumnName("TestParamTableName")]
            public string TableName { get; set; }

            [DataTableColumnName("TestParamConnectionSchemataFileName")]
            public string ConnectionSchemataFileName { get; set; }
        }

        private class CreateDataTableTestExpectedResults
        {
            [DataTableColumnName("ExpectedResultTableName")]
            public string TableName { get; set; }
        }
    }
}
